package utwente.sem.eindopdracht;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

/**
 * Een AssemblageLijn is de baan waarop Exemplaren van een bepaald Product gemaakt kunnen worden.
 * Door middel van gekoppelde Robots worden handelingen uitgevoerd op een Product-in-ontwikkeling.
 * 
 * @author Richard Heijblom & Mark Wienk
 *
 */
public class AssemblageLijn implements Observer {

	//STATES
	public static final int STATUS_IDLE = 0;
	public static final int STATUS_ASSIGNED = 1;
	public static final int STATUS_RUNNING = 2;
	public static final int STATUS_PAUSED = 3;
	public static final int STATUS_ERROR = 4;
	//TODO: STATE error implementeren

	
	/**
	 * @clientCardinality 1
	 * @supplierCardinality 0..1
	 */
	
	private ProductRun productRun;
	private int status;
	private int id;
	private List<Robot> robots;
	
	/**
	 * Een AssemblageLijn is de baan waarop Exemplaren van een bepaald Product gemaakt kunnen worden.
	 * Door middel van gekoppelde Robots worden handelingen uitgevoerd op een Product-in-ontwikkeling.
	 * @param id Het id-nummer van de AssemblageLijn.
	 */
	public AssemblageLijn(int id) {
		this.id = id;
		this.status = STATUS_IDLE;
		this.robots = new ArrayList<Robot>();
	}
	
	/**
	 * Verkrijg de status van een AssemblageLijn als nummer (gebruik getStatusNaam() voor een beschrijving van de status).
	 * @return status (int)
	 */
	public int getStatus() {
		return this.status;
	}
	
	/**
	 * Verkrijg de status als een String (voor weergave).
	 * @return de naam van de status
	 */
	public String getStatusNaam() {
		switch(status) {
			case STATUS_IDLE:
				return "IDLE";
			case STATUS_ASSIGNED:
				return "ASSIGNED";
			case STATUS_RUNNING:
				return "RUNNING";
			case STATUS_PAUSED:
				return "PAUSED";
			case STATUS_ERROR:
				return "ERROR";
			default:
				return "ERROR, NO STATE";
		}
	}
	
	/**
	 * Stel de status van de baan in
	 * @param status (int)
	 */
	public void setStatus(int status) {
		this.status = status;
	}
	
	/**
	 * Verkrijg de product run die aan de AssemblageLijn is gekoppeld.
	 * @return de gekoppelde ProductRun
	 */
	public ProductRun getProductRun() {
		return this.productRun;
	}
	
	/**
	 * Stel een product run in bij een AssemblageLijn.
	 * @param productRun
	 */
	public void setProductRun(ProductRun productRun) {
		robots.clear();
		this.productRun = productRun;
		Blauwdruk blauwdruk = productRun.getProduct().getBlauwdruk();
		for(int i = 0; i < blauwdruk.getAantalStappen(); i++) {
			robots.add(new Robot(i, blauwdruk.getStap(i), this));
			//TODO: STUB: Er wordt nu altijd een nieuwe Robot aangemaakt ipv een beschikbare robot gebruiken.
		}
		this.status = STATUS_ASSIGNED;
		Tui.getUI().toonBericht("ProductRun succesvol toegewezen aan AssemblageLijn");
	}
	
	public void clearProductRun() {
		robots.clear();
		this.productRun= null;
		this.status = STATUS_IDLE;
	}
	
	/**
	 * Verkrijg het id van de assemblagelijn.
	 * @return id-nummer (int)
	 */
	public int getId() {
		return this.id;
	}
	
	/**
	 * Stel het id-nummer van de AssemblageLijn in.
	 * @param id nummer (int)
	 */
	public void setId(int id) {
		this.id = id;
	}
	
	/**
	 * Verkijg de Robots die aan de AssemblageLijn zijn gekoppeld.
	 * @return een List<Robot> met robots in het systeem
	 */
	public List<Robot> getRobots() {
		return robots;
	}
	
	/**
	 * Check de status van de lijn, geef terug of deze startbaar is
	 * @return true als startbaar, false als niet startbaar
	 */
	public boolean isStartbaar() {
		boolean result = false;
		if(this.status == STATUS_ASSIGNED || this.status == STATUS_PAUSED) {
			result = true;
		}
		return result;
	}
	
	/**
	 * Check de status van de lijn, geef terug of deze pauzeerbaar is
	 * @return true als pauzeerbaar, false als niet pauzeerbaar
	 */
	public boolean isPauzeerbaar() {
		boolean result = false;
		if(this.status == STATUS_RUNNING || this.status == STATUS_ERROR) {
			result = true;
		}
		return result;
	}

	/**
	 * Start de AssemblageLijn.
	 */
	public void start() {
		this.status = STATUS_RUNNING;
		this.transporteer();
		for(Robot robot : robots) {
			new Thread(robot).start();
		}
	}

	/**
	 * Pauzeer de gehele AssemblageLijn
	 */
	public void pauzeer() {
		this.status = STATUS_PAUSED;
		for(Robot robot : robots) {
			robot.pauzeer();
		}
	}

	/**
	 * Transporteer alle Exemplaren naar de volgende Robot. De laatste Robot geeft een Serienummer aan zijn Exemplaar
	 */
	public void transporteer() {
		boolean inProductie = true;
		if(productRun.getAantalProducten() < 1) {
			inProductie = false;
			for(Robot robot : robots) {
				if(robot.getExemplaar() != null) {
					inProductie = true;
					break;
				}
			}
		}
		if(inProductie) {
			//Begin bij de laatste Robot
			for(int i = robots.size() - 1; i >= 0; i--) {
				//Voeg serienummer toe aan het Exemplaar bij de laatste Robot op de AssemblageLijn
				if(i == robots.size() - 1 && robots.get(i).getExemplaar() != null) 	{
					robots.get(i).getExemplaar().setSerienummer(new Date().hashCode());
				}
				
				//Tussenliggende Robots (verplaats Exemplaar van Robot i-1 naar Robot i
				if(i-1 >= 0) {
					robots.get(i).setExemplaar(robots.get(i-1).getExemplaar());
					robots.get(i-1).setExemplaar(null);
				}
			
				//De eerste Robot moet een nieuw Exemplaar krijgen
				if(i == 0 && productRun.getAantalProducten() > 0) {
					Exemplaar nieuwExemplaar = new Exemplaar();
					productRun.setAantalProducten(productRun.getAantalProducten()-1);
					productRun.getProduct().addExemplaar(nieuwExemplaar);
					robots.get(i).setExemplaar(nieuwExemplaar);
				}
			}
		} else {
			this.clearProductRun();
		}
	}

	/**
	 * Voeg een Robot toe aan de AssemblageLijn
	 * @param robot
	 */
	public void addRobot(Robot robot) {
		this.robots.add(robot);
	}
	
	@Override
	public void update(Observable arg0, Object arg1) {
		boolean alleRobotsWaiting = true;
		// Kijk of elke Robot status op waiting staat
		for(int i = 0; i < robots.size(); i++) {
			if(robots.get(i).getStatus() != Robot.STATUS_WAITING) {
				alleRobotsWaiting = false;
				break;
			}
		}

		// Als alle Robots op Waiting staan, transporteer Exemplaren
		if(alleRobotsWaiting) {
			transporteer();
		
			// Start de Robots.
			for(Robot robot : robots) {
				new Thread(robot).start();
			}
		}
	}
	
	/**
	 * Verkrijg informatie over de AssemblageLijn voor weergave in een lijst.
	 */
	public String toString() {
		return  "Status: " + this.getStatusNaam();
	}

	/**
	 * Toon de informatie over de assemblagelijn.
	 * @return de informatie van de assemblagelijn.
	 */
	public String showInfo() {
		return  "AssemblageLijn:\t " + this.id + "\n" +
				"Status:\t\t " + this.getStatusNaam() + "\n" +
				"ProductRun:\t " + 
				(this.productRun != null ? "\n" + this.productRun.showInfo() : "Geen product run toegewezen");
	}
	
	public void showAssemblageLijnMenu() {
		Tui.getUI().toonBericht(this.showInfo());
		String[] keuzeOpties = {
				"Terug naar het vorige menu",
				"Start/pauzeer lijn",
				"Wijs product run toe aan deze lijn",
				"Robot statusinformatie"
		};
		switch(Tui.getUI().toonMenu("Kies hieronder wat u wilt doen met de lijn", keuzeOpties)) {
		case 0:
			return;
		case 1:
			if(this.isStartbaar()) {
				this.start();
				Tui.getUI().toonBericht("AssemblageLijn " + this.id + " gestart.");
			}
			else if(this.isPauzeerbaar()) {
				this.pauzeer();
				Tui.getUI().toonBericht("AssemblageLijn " + this.id + " gepauzeerd.");
			}
			else {
				Tui.getUI().toonBericht("AssemblageLijn kan niet gepauzeerd of gestart worden.");
			}
			showAssemblageLijnMenu();
			break;
		case 2:
			ProductRun[] productRuns = new ProductRun[Controller.getController().getProductRunLijst().size()];
			productRuns = Controller.getController().getProductRunLijst().toArray(productRuns);
			if(productRuns.length > 0) {
				int productRunID = Tui.getUI().toonMenu("Kies een product run uit de lijst", productRuns);
				this.setProductRun(productRuns[productRunID]);
			} else {
				Tui.getUI().toonBericht("Er zijn nog geen product runs, maak deze nu eerst");
				ProductRun.showMenu();
			}
			showAssemblageLijnMenu();
			break;
		case 3:
			Tui.getUI().toonLijst("De volgende Robots zijn gekoppeld aan deze AssemblageLijn", robots.toArray());
			showAssemblageLijnMenu();
			break;
		}
	}
	
	/**
	 * Laat het menu met de opties voor AssemblageLijnen zien.
	 */
	public static void showMenu() {
		AssemblageLijn[] assemblageLijnen = Controller.getController().getAssemblageLijnen();
		int lijnNummer = Tui.getUI().toonMenu("Kies een assemblagelijn:", assemblageLijnen);
		assemblageLijnen[lijnNummer].showAssemblageLijnMenu();
	}
}
